# logger.py
# Logs channels/privmsgs


# Settings

# log_dir - Where logs are stored. Create it before starting the bot.
log_dir = "logs/"

# log_channels/log_messages
# Here you can specify what will be logged. To log everything add "*".
# To disable logging of a channel/nick you can prefix it with '-'.
log_channels = ["*", "-#test"]
log_messages = ["*"]


# strip_formatting - Set this to True to strip color/bold/underline/reverse from messages
strip_formatting = False

# Formats

logfmt = {}

logfmt["PRIVMSG"]        = "<{0}> {1}"
logfmt["PRIVMSG_CHAN"]   = "<{0}> {2}"
logfmt["OMSG"]           = "*{0}:{1}* {2}"
logfmt["NOTICE"]         = "-{0}- {1}"
logfmt["NOTICE_CHAN"]    = "-{0}:{1}- {2}"
logfmt["ACTION"]         = "* {0} {1}"
logfmt["ACTION_CHAN"]    = "* {0} {1}"
logfmt["CTCP"]           = "[{0} {1}] {2}"
logfmt["CTCP_SELF"]      = "-> [{0}] {1} {2}"
logfmt["CTCP_CHAN"]      = "[{0}:{1} {2}] {3}"
logfmt["CTCPREPLY"]      = "[{0} {1} Reply] {2}"
logfmt["CTCPREPLY_CHAN"] = "[{0}:{1} {2} Reply] {3}"
logfmt["TOPIC"]          = "*** {0} changes topic to '{1}'"
logfmt["TOPIC_IS"]       = "*** Topic is '{1}'"
logfmt["TOPIC_SETBY"]    = "*** Set by {0} on {2}"
logfmt["KICK"]           = "*** {0} was kicked by {2} ({3})"
logfmt["KICKME"]         = "*** You were kicked by {2} ({3})"
logfmt["JOIN"]           = "*** {0} ({1}@{2}) has joined {3}"
logfmt["PART"]           = "*** {0} ({1}@{2}) has left {3} ({4})"
logfmt["QUIT"]           = "*** {0} ({1}@{2}) Quit ({3})"
logfmt["NICK"]           = "*** {0} is now known as {1}"
#logfmt["MODE"]           = "*** {0} sets mode: {2}"
logfmt["MODE_CHAN"]      = "*** {0} sets mode: {4}"

logfmt["SESSION_START"] = "Session Start: {0}"
logfmt["SESSION_IDENT"] = "Session Ident: {0}"
logfmt["SESSION_TIME"]  = "Session Time: {0}"
logfmt["SESSION_CLOSE"] = "Session Close: {0}"

logfmt["TIMESTAMP"]     = "[%H:%M]"
logfmt["FULLDATE"]      = "%a %b %d %H:%M:%S %Y"

# TODO:
# - Document format strings
# - Log roatating
# - Follow nickname changes for privmsgs?
# - Make which events are logged configurable
# BUG: Messages the bot send will appear before the event they're responding to in the log.
#      Would be nice if we could specify addon execution order somehow.
###############################################################################

# Code below here :P

import time, datetime, re

irc = None

my_logs = {}

fstrip = re.compile(r"(?:\x03(?:\d{1,2}(?:,\d{1,2})?)?)|[\x02\x1F\x16\x0F]")

class LogFile():
    def __init__(self, fh = None, last_write = time.localtime()):
        self.fh = fh
        self.last_write = last_write

def init(bot):
    global irc
    irc = bot

    bot.cmd.events['PRIVMSG'].subscribe(handle_privmsg)
    bot.cmd.events['NOTICE'].subscribe(handle_notice)
    bot.cmd.events['JOIN'].subscribe(handle_join)
    bot.cmd.events['PART'].subscribe(handle_part)
    bot.cmd.events['MODE'].subscribe(handle_mode)
    bot.cmd.events['QUIT'].subscribe(handle_quit)
    bot.cmd.events['KICK'].subscribe(handle_kick)
    bot.cmd.events['TOPIC'].subscribe(handle_topic)
    bot.cmd.events['NICK'].subscribe(handle_nick)
    bot.cmd.events['PRIVMSG_SELF'].subscribe(handle_privmsg_self)
    bot.cmd.events['NOTICE_SELF'].subscribe(handle_notice_self)
    bot.cmd.events['332'].subscribe(handle_numeric)
    bot.cmd.events['333'].subscribe(handle_numeric)
    bot.cmd.events['DISCONNECT'].subscribe(handle_disconnect)

def unload():
    handle_disconnect(None, None)

def handle_privmsg(sender, args):
    target = args[2]
    name = ""
    chan = False
    format = ""

    if args[0][0] == '\001':
        return handle_ctcp(sender, args)

    t = target.lstrip(irc.user_prefixes)

    if irc.is_channel(t):
        name = t
        chan = True
        format = 'OMSG' if target != t else 'PRIVMSG_CHAN'
    else:
        name = sender.nick
    
    name = get_log(name)

    if not name:
        return

    if chan:
        print_log(name, format, sender.nick, target, args[0])
    else:
        print_log(name, 'PRIVMSG', sender.nick, args[0])

def handle_notice(sender, args):
    target = args[2]
    name = ""
    chan = False

    if args[0][0] == '\001':
        return handle_ctcpreply(sender, args)
    
    t = target.lstrip(irc.user_prefixes)

    if irc.is_channel(t):
        name = t
        chan = True
    else:
        name = sender.nick
    
    name = get_log(name)

    if not name:
        return

    if chan:
        print_log(name, 'NOTICE_CHAN', sender.nick, target, args[0])
    else:
        print_log(name, 'NOTICE', sender.nick, args[0])

def handle_privmsg_self(sender, args):
    if args[0][0] == '\001':
        return handle_ctcp_self(sender, args)
    targets = args[2].split(',')
    for target in targets:
        if not target:
            continue
        name = ""
        chan = False
        format = ""

        t = target.lstrip(irc.user_prefixes)

        if irc.is_channel(t):
            name = t
            chan = True
            format = 'OMSG' if target != t else 'PRIVMSG_CHAN'
        else:
            name = target
    
        name = get_log(name)

        if not name:
            return

        if chan:
            print_log(name, format, sender.nick, target, args[0])
        else:
            print_log(name, 'PRIVMSG', sender.nick, args[0])

def handle_notice_self(sender, args):
    if args[0][0] == '\001':
        return handle_ctcp_self(sender, args)
    targets = args[2].split(',')
    for target in targets:
        if not target:
            continue
        name = ""
        chan = False
        t = target.lstrip(irc.user_prefixes)

        if irc.is_channel(t):
            name = t
            chan = True
        else:
            name = target
    
        name = get_log(name)

        if not name:
            return

        if chan:
            print_log(name, 'NOTICE_CHAN', sender.nick, target, args[0])
        else:
            print_log(name, 'NOTICE', sender.nick, args[0])

def handle_ctcp(sender, args):
    target = args[2]
    name = ""
    chan = False
    t = target.lstrip(irc.user_prefixes)

    ctcp_msg = args[0].translate(None, '\001')
    ctcp_args = ctcp_msg.split(None, 1)
    if not ctcp_args:
        return
    ctcp_command = ctcp_args[0]
    ctcp_data = ctcp_args[1] if len(ctcp_args) > 1 else ""

    if irc.is_channel(t):
        name = t
        chan = True
    else:
        name = sender.nick

    name = get_log(name)

    if not name:
        return

    if chan:
        if ctcp_command == 'ACTION':
            print_log(name, 'ACTION_CHAN', sender.nick, ctcp_data)
        else:
            print_log(name, 'CTCP_CHAN', sender.nick, target, ctcp_command, ctcp_data)
    else:
        if ctcp_command == 'ACTION':
            print_log(name, 'ACTION', sender.nick, ctcp_data)
        else:
            print_log(name, 'CTCP', sender.nick, ctcp_command, ctcp_data)

def handle_ctcpreply(sender, args):
    target = args[2]
    name = ""
    chan = False
    t = target.lstrip(irc.user_prefixes)

    ctcpr_msg = args[0].translate(None, '\001')
    ctcpr_args = ctcpr_msg.split(None, 1)
    if not ctcpr_args:
        return
    ctcpr_command = ctcpr_args[0]
    ctcpr_data = ctcpr_args[1] if len(ctcpr_args) > 1 else ""
    
    if irc.is_channel(t):
        name = t
        chan = True
    else:
        name = sender.nick
    
    name = get_log(name)

    if not name:
        return
    
    if chan:
        print_log(name, 'CTCPREPLY_CHAN'. sender.nick, target, ctcpr_command, ctcpr_data)
    else:
        print_log(name, 'CTCPREPLY', sender.nick, ctcpr_command, ctcpr_data)

def handle_ctcp_self(sender, args):
    targets = args[2].split(',')
    ctcp_msg = args[0].translate(None, '\001')
    ctcp_args = ctcp_msg.split(None, 1)
    if not ctcp_args:
        return

    ctcp_command = ctcp_args[0]
    ctcp_data = ctcp_args[1] if len(ctcp_args) > 1 else ""
    
    for target in targets:
	chan = False
        if not target:
            continue
        name = ""
        t = target.lstrip(irc.user_prefixes)
        
        if irc.is_channel(t):
            chan = True

        name = get_log(t)

        if not name:
            return

        if chan:
            if ctcp_command == 'ACTION':
                print_log(name, 'ACTION_CHAN', sender.nick, ctcp_data)
            else:
                print_log(name, 'CTCP_SELF', target, ctcp_command, ctcp_data)
        else:
            if ctcp_command == 'ACTION':
                print_log(name, 'ACTION', sender.nick, ctcp_data)
            else:
                print_log(name, 'CTCP_SELF', target, ctcp_command, ctcp_data)

def handle_join(sender, args):
    target = args[0]
    name = ""

    name = get_log(target)

    if not name:
        return
    
    print_log(name, 'JOIN', sender.nick, sender.user, sender.host, args[0])

def handle_part(sender, args):
    target = args[2]
    name = ""

    name = get_log(target)

    if not name:
        return

    print_log(name, 'PART', sender.nick, sender.user, sender.host, target, args[0])
    if sender.nick == irc.nickname:
        close_log(name)
        del my_logs[name]

def handle_mode(sender, args):
    target = args[2]
    name = ""
    chan = False

    if irc.is_channel(target):
        chan = True
    else:
        # TODO: status log?
        return

    name = get_log(target)

    if not name:
        return

    mode_str = " ".join(args[3:])
    print_log(name, 'MODE_CHAN', sender.nick, sender.user, sender.host, target, mode_str)

def handle_quit(sender, args): 
    name = ""

    for channel in irc.chmgr.channels:
        if sender.nick == irc.nickname:
            name = get_log(channel)
            if name:
                print_log(name, 'QUIT', sender.nick, sender.user, sender.host, args[0])
                close_log(name)
                del my_logs[name]
        elif sender.nick in irc.chmgr.get(channel).users:
            name = get_log(channel)
            if name:
                print_log(name, 'QUIT', sender.nick, sender.user, sender.host, args[0])

def handle_kick(sender, args):
    channel = args[2]
    target = args[3]
    name = ""

    name = get_log(channel)
    if not name:
        return

    if target == irc.nickname:
        print_log(name, 'KICKME', target, channel, sender.nick, args[0])
        close_log(name)
        del my_logs[name]
    else:
        print_log(name, 'KICK', target, channel, sender.nick, args[0])

def handle_topic(sender, args):
    target = args[2]
    name = ""

    name = get_log(target)
    if not name:
        return
    
    print_log(name, 'TOPIC', sender.nick, args[0])

def handle_nick(sender, args):
    new_nick = args[0]

    for channel in irc.chmgr.channels:
        if sender.nick in irc.chmgr.get(channel).users:
            name = get_log(channel)
            if name:
                print_log(name, 'NICK', sender.nick, new_nick)

def handle_numeric(sender, args):
    if args[1] == '332':
        return handle_rpl_topic(sender, args)
    elif args[1] == '333':
        return handle_rpl_topic_set_by(sender, args)
    else:
        return

def handle_rpl_topic(sender, args):
    target = args[3]
    name = ""

    name = get_log(target)
    if not name:
        return
    
    print_log(name, 'TOPIC_IS', target, args[0])

def handle_rpl_topic_set_by(sender, args):
    target = args[3]
    name = ""

    name = get_log(target)
    if not name:
        return

    print_log(name, 'TOPIC_SETBY', args[4], args[3], time.strftime( \
                             logfmt['FULLDATE'], time.localtime(int(args[5]))))

def handle_disconnect(sender, args):
    for f in my_logs:
        close_log(f)
    my_logs.clear()

def get_log(target):
    global irc
    logging = False
    my_list = log_messages

    if not target:
        return

    name = irc.irclower(target)

    if irc.is_channel(target):
        my_list = log_channels

    if "*" in my_list:
        logging = True
        if ("-%s" % (target)) in my_list:
            logging = False
    else:
        if target in my_list:
            logging = True

    if not logging:
        return None

    if name not in my_logs:
        fn = log_dir + name + ".log"

        try:
            my_logs[name] = LogFile(open(fn, 'a', 0), time.localtime())
        except IOError:
            return

        try:
            print >>my_logs[name].fh, logfmt['SESSION_START'].format( \
                                        time.strftime(logfmt['FULLDATE']))
        except IOError:
            print "!#! Error writing to log file"
            return

    else:
        # Check last time we wrote to the file
        curtime = time.localtime()
        if curtime.tm_mday != my_logs[name].last_write.tm_mday:
            dt = datetime.datetime(curtime.tm_year, curtime.tm_mon, \
                curtime.tm_mday, 0, 0, 0)
            try:
                print >>my_logs[name].fh, logfmt['SESSION_TIME'].format( \
                                    dt.strftime(logfmt['FULLDATE']))
            except IOError:
                print "!#! Error writing to log file"
    return name

def print_log(name, format, *args):
    if not name or name not in my_logs:
        return
    if not format or format not in logfmt:
        return

    curtime = time.localtime()

    #print name + " " + format + " " + " ".join(args)
    try:
        line = logfmt[format].format(*args)
        if strip_formatting:
            line = fstrip.sub("", line)
        print >>my_logs[name].fh, time.strftime(logfmt['TIMESTAMP'], curtime) \
                                        + " " + line
        my_logs[name].last_write = curtime
    except IOError:
        print "!#! Error writing to log file"
                                            
def close_log(name):
    if not name or name not in my_logs:
        return

    curtime = time.localtime()

    try:
        print >>my_logs[name].fh, logfmt['SESSION_CLOSE'].format( \
                                    time.strftime(logfmt['FULLDATE'], curtime))
        my_logs[name].fh.write("\n")
        my_logs[name].fh.close()
    except IOError:
        print "!#! Error writing to log file"
